/*
 Copyright (c) 2003-2006, Septicus Software All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
 met:
 
 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer. 
 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution. 
 * Neither the name of Septicus Software nor the names of its contributors
 may be used to endorse or promote products derived from this software
 without specific prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
//
//  SSCrypto.m
//  SimpleWebCam
//
//  Created by Ed Silva on Sat May 31 2003.
//  Copyright (c) 2003-2006 Septicus Software. All rights reserved.
//

#import "SSCrypto.h"
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCryptor.h>
#import <Security/Security.h>


#define kChosenDigestLength CC_SHA1_DIGEST_LENGTH

@interface SSCrypto (PrivateAPI)
- (void)setupOpenSSL;
- (void)cleanupOpenSSL;
@end

// SSCrypto object
@implementation SSCrypto

/**
 * Generic constructor.
 * Simply configures internal OpenSSL setup.
**/
- (id)init
{
    if((self = [super init]))
	{
        // Call private method to handle the setup for internal OpenSSL stuff
		[self setupOpenSSL];
    }
    return self;
}


/**
 * Public key only constructor.
 * Configures the instance to use non-symmetric encryption/decryption, and to use the given public key.
**/
- (id)initWithPublicKey:(NSData *)pub
{
    return [self initWithPublicKey:pub privateKey:nil];
}

/**
 * Private key only constructor.
 * Configures the instance to use non-symmetric encryption/decryption, and to use the given private key.
**/
- (id)initWithPrivateKey:(NSData *)priv
{
    return [self initWithPublicKey:nil privateKey:priv];
}

/**
 * Public and Private key constructor.
 * Configures the instance to use non-symmetric encryption/decryption, and to use the given public and private keys.
**/
- (id)initWithPublicKey:(NSData *)pub privateKey:(NSData *)priv;
{
    if((self = [super init]))
	{
		// Call private method to handle the setup for internal OpenSSL stuff
		[self setupOpenSSL];
		
		// Store the publicKey variable (if not nil)
		if(pub != nil)
			[self setPublicKey:pub];
		
		// Store the privateKey variable (if not nil)
		if(priv != nil)
			[self setPrivateKey:priv];
    }
    return self;
}

/**
 * This method sets up everything needed to use the OpenSSL libraries later within the code.
 * This method should be called by every constructor.
**/
- (void)setupOpenSSL
{
	// OpenSSL keeps an internal table of digest algorithms and ciphers.
	// It uses this table to lookup ciphers via functions such as EVP_get_cipher_byname().
	// OpenSSL_add_all_digests() adds all digest algorithms to the table.
	// OpenSSL_add_all_ciphers() adds all cipher algorithms to the table.
	// OpenSSL_add_all_algorithms() adds all algorithms to the table (digests and ciphers).
	// EVP_cleanup() removes all ciphers and digests from the table.
	// 
	// A typical application will call OpenSSL_add_all_algorithms() initially and EVP_cleanup() before exiting.
	
	OpenSSL_add_all_algorithms();
	
	// ERR_load_crypto_strings() registers the error strings for all libcrypto functions.
	// SSL_load_error_strings() does the same, but also registers the libssl error strings.
	// ERR_free_strings() frees all previously loaded error strings.
	//
	// One of these functions should be called before generating textual error messages.
	// However, this is not required when memory usage is an issue.
	
	ERR_load_crypto_strings();
}

/**
 * Standard deallocation method.
**/
//- (void)dealloc
//{
//    // Cleanup all OpenSSL stuff
//	[self cleanupOpenSSL];
//	
//	// Release all instance variables
//	[cipherText release];
//	[clearText release];
//	[publicKey release];
//	[privateKey release];
//	
//	// Move up the inheritance chain
//    [super dealloc];
//}

- (void)cleanupOpenSSL
{
	// EVP_cleanup() removes all ciphers and digests from the table.
	EVP_cleanup();
	
	// ERR_free_strings() frees all previously loaded error strings.
    ERR_free_strings();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Getter, Setter Methods:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


/**
 * Returns the public key currently in use.
**/
- (NSData *)publicKey
{
    return publicKey;
}

/**
 * Sets the public key to use.
 * Public keys are used to verify signed data, or to encrypt data. (ToDo)
**/
- (void)setPublicKey:(NSData *)k
{
//    [k retain];
//    [publicKey release];
    publicKey = k;
}

/**
 * Returns the private key currently in use.
**/
- (NSData *)privateKey
{
    return privateKey;
}

/**
 * Sets the private key to use.
 * Private keys are used to sign data, or to decrypt data. (ToDo)
 * 
 * The data that is provided should from a file (such as private.pem) that was generated by openssl.
**/
- (void)setPrivateKey:(NSData *)k
{
//    [k retain];
//    [privateKey release];
    privateKey = k;
}

/**
 * Returns the clear text as plain NSData.
 * The plain text contains the text that was previously set.
 * It's the known text, which is to be encrypted, decrypted, etc.
**/
- (NSData *)clearTextAsData
{
    return clearText;
}

/**
 * Returns the clear text formatted as an NSString.
 * The plain text contains the text that was previously set.
 * It's the known text, which is to be encrypted, decrypted, etc.
**/
- (NSString *)clearTextAsString
{
    return [[NSString alloc] initWithData:[self clearTextAsData] encoding:NSUTF8StringEncoding] ;
}

/**
 * Sets the clear text using the given data.
 * The clear text will be used for encryption, decryption, etc.
 *
 * Note that the given data reference is retained and later used, so it shouldn't be externally modified.
**/
- (void)setClearTextWithData:(NSData *)c
{
//    [c retain];
//    [clearText release];
    clearText = c;
}

/**
 * Sets the clear text using the given string.
 * The clear text will be used for encryption, signing, and digests.
**/
- (void)setClearTextWithString:(NSString *)c
{
//	[clearText release];
    
	// BUG FIX : PLL (2009/02/21)
	//
	// [c length] : Returns the number of Unicode characters in the receiver.
	// For example "‚àö¬©‚àö‚Ä†‚àö√ü test" in UTF8 is 11 bytes (c3 a9 c3 a0 c3 a7 20 74 65 73 74)
	// but only 8 Unicode characters.
	// So this will truncate the text and result in one error.
	//
	// clearText = [[NSData alloc] initWithBytes:[c UTF8String] length:[c length]];
	
	// The number of bytes required to store the receiver in the encoding enc in a non-external representation. The length does not include space for a terminating NULL character.
	unsigned int length = [c lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
	clearText = [[NSData alloc] initWithBytes:[c UTF8String] length:length];
}

/**
 * Returns the cipher text as plain NSData.
 * The cipher text contains the most recent encrypted data.  (Result of call to encrypt)
**/
- (NSData *)cipherTextAsData
{
    return cipherText;
}

/**
 * Returns the ciper text formatted as an NSString.
 * The ciper text contains the most recent encrypted data.  (Result of call to encrypt)
**/
- (NSString *)cipherTextAsString
{
    return [[NSString alloc] initWithData:[self cipherTextAsData] encoding:NSUTF8StringEncoding];
}

/**
 * Sets the cipher text using the given data.
 * The cipher text will be used for decryption and verifying.
**/
- (void)setCipherText:(NSData *)c
{
//    [c retain];
//    [cipherText release];
    cipherText = c;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Decryption methods:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Decrypts the cipher text data.
 * If symmetric decryption is being used, then the decryption is done using the given cipher.
 * Otherwise, asymmetric decryptions is used, and the data is encrypted using the private key.
 *
 * Returns the clear text data that is the result of the decryption.
 * The resulting clear text data may also be later retrieved with the clearTextAsData or clearTextAsString methods.
**/
- (NSData *)decrypt
{
	// If there is no cipher text set, or the cipher text is an empty string (zero length data)
	// then there is nothing to decrypt, and we may as well return nil
    if(cipherText == nil || [cipherText length] == 0)
	{
        return nil;
    }
    
    unsigned char *outbuf;
    int outlen, inlen;
    inlen = [cipherText length];
    unsigned char *input = (unsigned char *)[cipherText bytes];
    
    // Use asymmetric decryption...
		
    if([self privateKey] == nil)
    {
        NSLog(@"Cannot decrypt without the private key, which is currently nil");
        return nil;
    }
        
    BIO *privateBIO = NULL;
    RSA *privateRSA = NULL;
		
    if(!(privateBIO = BIO_new_mem_buf((unsigned char*)[[self privateKey] bytes], [[self privateKey] length])))
    {
        NSLog(@"BIO_new_mem_buf() failed!");
        return nil;
    }
		
    if(!PEM_read_bio_RSAPrivateKey(privateBIO, &privateRSA, NULL, NULL))
    {
        NSLog(@"PEM_read_bio_RSAPrivateKey() failed!");
        return nil;
    }
		
    // RSA_check_key() returns 1 if rsa is a valid RSA key, and 0 otherwise.
		
    unsigned long check = RSA_check_key(privateRSA);
    if(check != 1)
    {
        NSLog(@"RSA_check_key() failed with result %ld!", check);
        return nil;
    }
		
    // RSA_size() returns the RSA modulus size in bytes.
    // It can be used to determine how much memory must be allocated for an RSA encrypted value.
		
    outbuf = (unsigned char *)malloc(RSA_size(privateRSA));
        
    if(!(outlen = RSA_private_decrypt(inlen, input, outbuf, privateRSA, RSA_PKCS1_PADDING)))
    {
        NSLog(@"RSA_private_decrypt() failed!");
        return nil;
    }
        
    if(outlen == -1)
    {
        NSLog(@"Decrypt error: %s (%s)",ERR_error_string(ERR_get_error(), NULL),ERR_reason_error_string(ERR_get_error()));
            return nil;
    }
    if (privateBIO) BIO_free(privateBIO);
    if (privateRSA) RSA_free(privateRSA);
    
	
	// Store the decrypted data as the clear text
    [self setClearTextWithData:[NSData dataWithBytes:outbuf length:outlen]];
    
	// Release the outbuf, since it was malloc'd
    if (outbuf) {
        free(outbuf);
    }
    
    return [self clearTextAsData];
}

- (NSData *)encrypt
{
	// If there is no clear text set, or the clear text is an empty string (zero length data)
	// then there is nothing to encrypt, and we may as well return nil
    if(clearText == nil || [clearText length] == 0)
	{
		return nil;
    }

    unsigned char *input = (unsigned char *)[clearText bytes];
    unsigned char *outbuf;
    int outlen, inlen;
    inlen = [clearText length];
    
   
		// Perform asymmetric encryption...
		
    if([self publicKey] == nil)
		{
        NSLog(@"Cannot encrypt without the public key, which is currently nil");
        return nil;
    }
    
    BIO *publicBIO = NULL;
    RSA *publicRSA = NULL;
    
    if(!(publicBIO = BIO_new_mem_buf((unsigned char*)[[self publicKey] bytes], [[self publicKey] length])))
    {
        NSLog(@"BIO_new_mem_buf() failed!");
        return nil;
    }
        
    if(!PEM_read_bio_RSA_PUBKEY(publicBIO, &publicRSA, NULL, NULL))
    {
        NSLog(@"PEM_read_bio_RSA_PUBKEY() failed!");
        return nil;
    }
        
    // RSA_size() returns the RSA modulus size in bytes.
    // It can be used to determine how much memory must be allocated for an RSA encrypted value.
		
    outbuf = (unsigned char *)malloc(RSA_size(publicRSA));
    
    if(!(outlen = RSA_public_encrypt(inlen, input, (unsigned char*)outbuf, publicRSA, RSA_PKCS1_PADDING)))
    {
        NSLog(@"RSA_public_encrypt failed!");
        return nil;
    }
    
    if(outlen == -1)
    {
        NSLog(@"Encrypt error: %s (%s)",ERR_error_string(ERR_get_error(), NULL),ERR_reason_error_string(ERR_get_error()));
        return nil;
    }
		
    if (publicBIO) BIO_free(publicBIO);
    if (publicRSA) RSA_free(publicRSA);
    
	
	// Store the encrypted data as the cipher text
    [self setCipherText:[NSData dataWithBytes:outbuf length:outlen]];
    
	// Release the outbuf, since it was malloc'd
    if(outbuf) {
        free(outbuf);
    }
    
    return [self cipherTextAsData];
}


+ (NSData *)generateRSAPrivateKeyWithLength:(int)length
{
    RSA *key = NULL;
    do {
        key = RSA_generate_key(length, RSA_F4, NULL, NULL);
    } while (1 != RSA_check_key(key));

    BIO *bio = BIO_new(BIO_s_mem());

    if (!PEM_write_bio_RSAPrivateKey(bio, key, NULL, NULL, 0, NULL, NULL))
    {
        NSLog(@"cannot write private key to memory");
        return nil;
    }
    if (key) RSA_free(key);

    char *pbio_data = NULL;
    int data_len = BIO_get_mem_data(bio, &pbio_data);
    NSData *result = [NSData dataWithBytes:pbio_data length:data_len];
    
    if (bio)
        BIO_free(bio);

    return result;
}

+ (NSData *)generateRSAPublicKeyFromPrivateKey:(NSData *)privateKey
{
    BIO *privateBIO = NULL;
	RSA *privateRSA = NULL;
	
	if (!(privateBIO = BIO_new_mem_buf((unsigned char*)[privateKey bytes], [privateKey length])))
	{
		NSLog(@"BIO_new_mem_buf() failed!");
		return nil;
	}
	
	if (!PEM_read_bio_RSAPrivateKey(privateBIO, &privateRSA, NULL, NULL))
	{
		NSLog(@"PEM_read_bio_RSAPrivateKey() failed!");
		return nil;
	}
	
	// RSA_check_key() returns 1 if rsa is a valid RSA key, and 0 otherwise.
	
	unsigned long check = RSA_check_key(privateRSA);
	if (check != 1)
	{
		NSLog(@"RSA_check_key() failed with result %ld!", check);
		return nil;
	}			

    BIO *bio = BIO_new(BIO_s_mem());

    if (!PEM_write_bio_RSA_PUBKEY(bio, privateRSA))
    {
        NSLog(@"cannot write public key to memory");
        return nil;
    }
    if (privateRSA) RSA_free(privateRSA);

    char *pbio_data = NULL;
    int data_len = BIO_get_mem_data(bio, &pbio_data);
    NSData *result = [NSData dataWithBytes:pbio_data length:data_len];
    
    if (bio)
        BIO_free(bio);

    return result;
}


+ (NSData *)getSHA1ForData:(NSData *)d
{
    unsigned length = [d length];
    const void *buffer = [d bytes];
    unsigned char *md = (unsigned char *)calloc(SHA_DIGEST_LENGTH, sizeof(unsigned char));
    NSAssert((md != NULL), @"Cannot calloc memory for buffer.");

    (void)SHA1(buffer, length, md);

    return [NSData dataWithBytesNoCopy:md length:SHA_DIGEST_LENGTH freeWhenDone:YES];
}



+ (NSString*) base64Encode:(NSData *)data
{
    static char base64EncodingTable[64] = {
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
        'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
        'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
    };
    int length = [data length];
    unsigned long ixtext, lentext;
    long ctremaining;
    unsigned char input[3], output[4];
    short i, charsonline = 0, ctcopy;
    const unsigned char *raw;
    NSMutableString *result;
    
    lentext = [data length];
    if (lentext < 1)
        return @"";
    result = [NSMutableString stringWithCapacity: lentext];
    raw = [data bytes];
    ixtext = 0;
    
    while (true) {
        ctremaining = lentext - ixtext;
        if (ctremaining <= 0)
            break;
        for (i = 0; i < 3; i++) {
            unsigned long ix = ixtext + i;
            if (ix < lentext)
                input[i] = raw[ix];
            else
                input[i] = 0;
        }
        output[0] = (input[0] & 0xFC) >> 2;
        output[1] = ((input[0] & 0x03) << 4) | ((input[1] & 0xF0) >> 4);
        output[2] = ((input[1] & 0x0F) << 2) | ((input[2] & 0xC0) >> 6);
        output[3] = input[2] & 0x3F;
        ctcopy = 4;
        switch (ctremaining) {
            case 1:
                ctcopy = 2;
                break;
            case 2:
                ctcopy = 3;
                break;
        }
        
        for (i = 0; i < ctcopy; i++)
            [result appendString: [NSString stringWithFormat: @"%c", base64EncodingTable[output[i]]]];
        
        for (i = ctcopy; i < 4; i++)
            [result appendString: @"="];
        
        ixtext += 3;
        charsonline += 4;
        
        if ((length > 0) && (charsonline >= length))
            charsonline = 0;
    }
    return result;
}

// 与java对接的签名方法 返回base64 encode值
- (NSString *)signString:(NSData *)message
{
    unsigned char *outbuf;
	if([self privateKey] == nil)
	{
		NSLog(@"Cannot sign (encrypt) without the private key, which is currently nil");
		return nil;
	}
	
	BIO *privateBIO = NULL;
	RSA *privateRSA = NULL;
	
	if(!(privateBIO = BIO_new_mem_buf((unsigned char*)[[self privateKey] bytes], [[self privateKey] length])))
	{
		NSLog(@"BIO_new_mem_buf() failed!");
		return nil;
	}
	
	if(!PEM_read_bio_RSAPrivateKey(privateBIO, &privateRSA, NULL, NULL))
	{
		NSLog(@"PEM_read_bio_RSAPrivateKey() failed!");
		return nil;
	}
	
	// RSA_check_key() returns 1 if rsa is a valid RSA key, and 0 otherwise.
	
	unsigned long check = RSA_check_key(privateRSA);
	if(check != 1)
	{
		NSLog(@"RSA_check_key() failed with result %ld!", check);
		return nil;
	}
	// RSA_size() returns the RSA modulus size in bytes.
	// It can be used to determine how much memory must be allocated for an RSA encrypted value.
	
	outbuf = (unsigned char *)malloc(RSA_size(privateRSA));
    //uint8_t * digest = (uint8_t *) malloc(SHA_DIGEST_LENGTH);
    
    unsigned char *digest = [[SSCrypto getSHA1ForData:message] bytes];
 
    //SHA1([message bytes], 4, digest);
    
    unsigned int outLen = RSA_size(privateRSA);
    uint8_t *_out = (uint8_t *) malloc(outLen);
    
    RSA_sign(NID_sha1, digest, SHA_DIGEST_LENGTH, _out, &outLen, privateRSA);
    
    NSData *resultData = [NSData dataWithBytesNoCopy:_out length:outLen freeWhenDone:YES];
    
    return [SSCrypto base64Encode:resultData];
}

@end
